﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.LiveFX.ResourceModel;
using System.Net;
using System.Windows.Media.Imaging;

namespace Wintellect.LiveFx.Cloudboard
{
  class DataAccessLayer
  {
    private bool offline;
    private Clipboard owner;
    private MeshResource mesh;
    private MeshObjectResource meshObject;
    private DataFeedResource dataFeed;
    private LiveFxClient meshClient;
    private NotificationManager notificationManager;

    public event EventHandler ItemsChanged;
    public event EventHandler OfflineChanged;

    public DataAccessLayer(Clipboard owner)
    {
      this.offline = false;
      this.owner = owner;
      this.meshClient = new LiveFxClient();

      try
      {
        this.mesh = this.RetrieveMesh();
        this.meshObject = this.RetrieveMeshObject();
        this.dataFeed = this.RetrieveDataFeed();

        if (!this.IsLocalDeviceMapped())
        {
          this.MapLocalDevice();
        }

        this.SubscribeToNotifications();
      }
      catch (Exception e)
      {
        if (!this.HandleException(e)) throw;
      }
    }

    private MeshResource RetrieveMesh()
    {
      ServiceDocumentResource serviceDocument = this.meshClient.ReadServiceDocument();
      return serviceDocument.ReadMesh(this.meshClient);
    }

    private MeshObjectResource RetrieveMeshObject()
    {
      MeshObjectResource output = new MeshObjectResource(this.owner.Name);
      output.Summary = this.owner.Description;
      output.Categories.Add(new System.ServiceModel.Syndication.SyndicationCategory(
        "Cloudboard",
        "http://wintellect.com/Cloudboard",
        null));
      output.MakeLiveMeshFolder();
      output = this.mesh.AccessMeshObject(this.meshClient, output, AccessMode.OpenAlways);

      this.owner.Description = output.Summary;
      this.owner.Created = output.PublishDate;
      this.owner.LastUpdated = output.LastUpdatedTime;

      return output;
    }

    private DataFeedResource RetrieveDataFeed()
    {
      DataFeedResource output = null;

#if ALWAYS_RECREATE
      // For debugging: the code below deletes our mesh object so we can start over from scratch
      if (this.Mesh != null)
      {
        this.MeshObject.Delete(this.meshClient);
      }
#endif

      //String query = String.Format("$filter=(Title eq '{0}')", this.owner.Name);

      output = new DataFeedResource(this.owner.Name);
      output.MakeLiveMeshFilesContainer();
      output = this.MeshObject.AccessDataFeed(this.meshClient, output, AccessMode.OpenAlways); 

      return output;
    }

    public bool Offline
    {
      get
      {
        return this.offline;
      }
      private set
      {
        if (this.offline != value)
        {
          this.offline = value;
          this.OnOfflineChanged();
        }
      }
    }

    private void OnOfflineChanged()
    {
      if (this.OfflineChanged != null)
      {
        this.OfflineChanged(this, new EventArgs());
      }
    }

    public String GetProvisionedUserName()
    {
      return this.Mesh.ProvisionedUser.Name;
    }

    public Object ReadMediaResource(DataEntryResource dataentry)
    {
      Object output = null;

      String fileExtension = System.IO.Path.GetExtension(dataentry.Title);
      if (fileExtension.Length > 1)
      {
        fileExtension = fileExtension.Substring(1);   // Skip the '.'
      }
      else
      {
        fileExtension = String.Empty;
      }

      try
      {
        using (HttpWebResponse response = dataentry.ReadMediaResource(this.meshClient))
        {
          ClipDataInfo cdi = (from cd in ClipboardHelper.s_ClipData
                              where String.Equals(fileExtension, cd.FileExtension, StringComparison.OrdinalIgnoreCase)
                              select cd).First();

          if (cdi.DataType == ClipDataType.Bitmap)
          {
            using (System.IO.Stream responseStream = response.GetResponseStream())
            {
              JpegBitmapDecoder decoder = new JpegBitmapDecoder(responseStream,
                 BitmapCreateOptions.DelayCreation, BitmapCacheOption.Default);
              BitmapSource bitmapSource = decoder.Frames[0];
              output = bitmapSource;
            }
          }

          if (cdi.DataType == ClipDataType.Text)
          {
            // Read the response stream into a Byte[] buffer

            // BUGBUG: Should this not be response.ContentLength?
            Byte[] buffer = new Byte[dataentry.MediaResource.Length];
            using (System.IO.Stream responseStream = response.GetResponseStream())
            {
              responseStream.Read(buffer, 0, buffer.Length);
            }
            String text = Encoding.UTF8.GetString(buffer);
            output = text;
          }
        }
      }
      catch (Exception e)
      {
        if (!this.HandleException(e)) throw;
      }

      return output;
    }

    public DataEntryResource AddDataEntry(ClipDataInfo cdi, System.IO.Stream dataStream)
    {
      DataEntryResource output = null;
      String slug = DateTime.Now.ToString("u").Replace(':', '.') + "." + cdi.FileExtension;
      try
      {
        DataFeedResource feed = this.DataFeed;

        if (feed != null)
        {
          output = feed.CreateMediaResource(
            this.meshClient,
            slug,
            cdi.Mime,
            dataStream,
            null,
            null); // TODO: Entry type?
        }

      }
      catch (Exception e)
      {
        if (!this.HandleException(e)) throw;
      }
      return output;
    }

    public void DeleteDataEntry(DataEntryResource dataEntry)
    {
      try
      {
        dataEntry.Delete(this.meshClient);
      }
      catch (Exception e)
      {
        if (!this.HandleException(e)) throw;
      }
    }

    public IEnumerable<MemberResource> Members
    {
      get
      {
        IEnumerable<MemberResource> output = null;
        try
        {
          output = this.MeshObject.ReadMembers(this.meshClient).Entries;
        }
        catch (Exception e)
        {
          if (!this.HandleException(e)) throw;
        }
        return output;
      }
    }

    public void AddMember(MemberResource member)
    {
      try
      {
        this.MeshObject.CreateMember(this.meshClient, member);
      }
      catch (Exception e)
      {
        if (!this.HandleException(e)) throw;
      }
    }

    public void DeleteMember(MemberResource member)
    {
      try
      {
        member.Delete(this.meshClient);
      }
      catch (Exception e)
      {
        if (!this.HandleException(e)) throw;
      }
    }

    private void SubscribeToNotifications()
    {
      try
      {
        this.notificationManager = new NotificationManager(this.meshClient, this.mesh);
        notificationManager.Subscribe(this.DataFeed.ReadDataEntries(this.meshClient), SynchClipboardItems);
        notificationManager.Start();
      }
      catch (Exception e)
      {
        if (!this.HandleException(e)) throw;
      }
    }

    private void SynchClipboardItems(LiveFxClient client, Uri feedUri)
    {
      // TODO - why isn;t this working as expected?
      //if (this.DataFeed.SelfLink != feedUri) 
      //  return;

      this.OnItemsChanged();
    }

    private void OnItemsChanged()
    {
      EventHandler itemsChanged = this.ItemsChanged;
      if (itemsChanged != null)
      {
        itemsChanged(this, new EventArgs());
      }
    }

    public MeshResource Mesh
    {
      get
      {
        return this.mesh;
      }
    }

    public MeshObjectResource MeshObject
    {
      get
      {
        return this.meshObject;
      }
    }

    public DataFeedResource DataFeed
    {
      get
      {
        return this.dataFeed;
      }
    }

    public IList<ClipboardItem> Items
    {
      get
      {
        IList<ClipboardItem> output = new List<ClipboardItem>();
        ResourceCollection<DataEntryResource> feedEntries = this.DataFeed.ReadDataEntries(this.meshClient);

        foreach (DataEntryResource entry in feedEntries.Entries)
        {
          Object media = this.ReadMediaResource(entry);
          ClipboardItem item = new ClipboardItem() { Resource = entry, Media = media };
          output.Add(item);
        }

        return output;
      }
    }

    public void DeleteMeshObject()
    {
      this.notificationManager.Unsubscribe(this.DataFeed.ReadDataEntries(this.meshClient));
      this.MeshObject.Delete(this.meshClient);
    }

    private bool IsLocalDeviceMapped()
    {
      return this.GetLocalDevice(false) != null;
    }

    private MeshDeviceResource MapLocalDevice()
    {
      return this.GetLocalDevice(true);
    }

    private MeshDeviceResource GetLocalDevice(bool createIfMissing)
    {
      MeshDeviceResource output;
      MeshResource mesh = this.Mesh;

      // Map MeshObject to a device
      ResourceCollection<MeshDeviceResource> meshDevices = mesh.ReadDevices(this.meshClient);
      output = (from mdx in meshDevices.Entries where mdx.IsLocal select mdx).FirstOrDefault();

      // The local device is installed
      if (output != null)
      {
        MeshObjectResource meshObject = this.MeshObject;
        System.Diagnostics.Debug.Assert(
          meshObject != null,
          String.Format("A mesh object should have been retrieved for the application named '{0}'", this.owner.Name));

        ResourceCollection<MappingResource> mappings = meshObject.ReadMappings(this.meshClient);
        // Check to see if the local device has been mapped
        MappingResource mapping = (from mappedDevice in mappings.Entries where mappedDevice.DeviceLink == output.SelfLink select mappedDevice).FirstOrDefault();

        // We were unable to find a mapping
        if (null == mapping)
        {
          mapping = new MappingResource("MAPPING_NAME") { DeviceLink = output.SelfLink };
          mapping = meshObject.CreateMapping(this.meshClient, mapping);
        }
      }
      return output;
    }

    private bool HandleException(Exception e)
    {
      bool output = false;
      if (e is LiveFxException<UnableToConnectToLoe>)
      {
        EtwTrace.Trace(
          "Data access error - the LOE does not appear to be running:\n{0}",
          e);
        this.Offline = true;
        output = true;
      }
      else if (e is LiveFxException<UnableToAccessLoeData>)
      {
        EtwTrace.Trace(
          "Data access error - a user does not appear to be signed into the LOE:\n{0}",
          e);
        this.Offline = true;
        output = true;
      }
      return output;
    }
  }
}
